using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.IO;
using System.Xml;

namespace CodePlex.OpenSearch
{

    /// <summary>
    /// Serves as a convenient base <see cref="IHttpHandler"/> for implementing the opensearch 1.1 specification.
    /// </summary>
    public abstract class SearchHandler : IHttpHandler
    {

        private readonly SearchHandlerSettings _handlerSettings;
       

        /// <summary>
        /// Gets a value indicating whether another request can use the <see cref="IHttpHandler"/>
        /// </summary>
        public abstract bool IsReusable { get; }

        /// <summary>
        /// Provides the opensearch description information for this search engine.
        /// </summary>
        protected abstract void Define(SearchDefinition definition);

        /// <summary>
        /// Offers a convention by which this search engine can return a set of search term completions for a given search prefix. The search completions can be used by a search client to dynamically present the end user with search term suggestions.
        /// </summary>
        protected abstract IEnumerable<string> GetSuggestions(string searchTerms);

        /// <summary>
        /// Provides custom search results for the given search terms.  (startPage is one-based)
        /// </summary>
        protected abstract SearchResponse Search(HttpContext context, int startPage, string searchTerms); 

        /// <summary>
        /// Process a request to this handler not directly related the opensearch infrastructure.  Can be no-op.
        /// </summary>
        protected abstract void ProcessDefault(HttpContext context);






        /// <summary>
        /// Base constructor for <see cref="SearchHandler"/>.
        /// </summary>
        protected SearchHandler()
        {
            SearchDefinition def = new SearchDefinition();

            this.Define(def);

            _handlerSettings = def.Settings;

        }

      
        /// <summary>
        /// Processes the http request.
        /// </summary>
        public void ProcessRequest(HttpContext context)
        {
            if (Util.RelativePath(context) == _handlerSettings.DescriptionPath)
            {
                this.ProcessDescription(context);
            }
            else if (Util.RelativePath(context) == _handlerSettings.SuggestPath)
            {
                this.ProcessSuggest(context);
            }
            else if (Util.RelativePath(context) == _handlerSettings.SearchPath)
            {
                this.ProcessSearch(context);
            }
            else if (Util.RelativePath(context) == _handlerSettings.ImageIco16Path)
            {
                this.ProcessImage(context,Constants.MimeTypes.ImageVndMicrosoftIcon,_handlerSettings.ImageIco16Source);
            }
            else if (Util.RelativePath(context) == _handlerSettings.ImagePng64Path)
            {
                this.ProcessImage(context, Constants.MimeTypes.ImagePng, _handlerSettings.ImagePng64Source);
            } 
            else 
            {
                this.ProcessDefault(context);
            } 
        }


     








        /// <summary>
        /// Renders the search description auto-discovery link tag to the provided <see cref="XmlTextWriter"/>.
        /// </summary>
        protected void RenderSearchDescriptionAutodiscovery(XmlTextWriter writer)
        {
            writer.WriteStartElement("link");
            writer.WriteAttributeString("rel", "search");
            writer.WriteAttributeString("type", Constants.MimeTypes.ApplicationOpenSearchDescriptionXml);
            writer.WriteAttributeString("href", _handlerSettings.DescriptionUrl);
            writer.WriteAttributeString("title", _handlerSettings.OpenSearchServerDescription.ShortName);
            writer.WriteEndElement();
        }

        /// <summary>
        /// Renders the appropriate meta tags for the given <see cref="SearchResponse"/> to the provided <see cref="XmlTextWriter"/>.
        /// </summary>
        protected void RenderSearchResponseMetaTags(XmlTextWriter writer, SearchResponse response)
        {
            writer.WriteStartElement("meta");
            writer.WriteAttributeString("name", "totalResults");
            writer.WriteAttributeString("content", response.TotalResults.ToString());
            writer.WriteEndElement();   // meta

            writer.WriteStartElement("meta");
            writer.WriteAttributeString("name", "startIndex");
            writer.WriteAttributeString("content", response.StartIndex.ToString());
            writer.WriteEndElement();   // meta

            writer.WriteStartElement("meta");
            writer.WriteAttributeString("name", "itemsPerPage");
            writer.WriteAttributeString("content", response.ItemsPerPage.ToString());
            writer.WriteEndElement();   // meta
        }



        /// <summary>
        /// Renders the standard feed auto-discovery link tags to the provided <see cref="XmlTextWriter"/>.
        /// </summary>
        protected void RenderSearchFeedsAutodiscovery(XmlTextWriter writer, SearchResponse response)
        {
            using (Util.StartElement(writer,"link"))
            {
                writer.WriteAttributeString("rel","alternate");
                writer.WriteAttributeString("type",Constants.MimeTypes.ApplicationRssXml);
                writer.WriteAttributeString("title", response.SearchTitle);
                writer.WriteAttributeString("href",this.GetSearchUrl(Constants.MimeTypes.ApplicationRssXml,response.SearchTerms,response.StartPage));
            }

            using (Util.StartElement(writer, "link"))
            {
                writer.WriteAttributeString("rel", "alternate");
                writer.WriteAttributeString("type", Constants.MimeTypes.ApplicationAtomXml);
                writer.WriteAttributeString("title", response.SearchTitle);
                writer.WriteAttributeString("href", this.GetSearchUrl(Constants.MimeTypes.ApplicationAtomXml, response.SearchTerms, response.StartPage));
            }
        }


        /// <summary>
        /// Processes the given <see cref="SearchResponse"/> rendered as user-readable xhtml.
        /// </summary>
        virtual protected void ProcessSearchXhtml(HttpContext context, SearchResponse response)
        {
            context.Response.ContentType = this.GetContentType(Constants.MimeTypes.TextHtml);   // for ie7


            XmlTextWriter writer = new XmlTextWriter(context.Response.Output);


            writer.Formatting = Formatting.Indented;

            writer.WriteStartDocument();


            writer.WriteDocType("html", "-//W3C//DTD XHTML 1.0 Strict//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd", null);

            writer.WriteStartElement("html");
            writer.WriteAttributeString("xmlns", Constants.Namespaces.Xhtml);
            writer.WriteAttributeString("xml:lang", "en");
            writer.WriteAttributeString("lang", "en");

            writer.WriteStartElement("head");
            writer.WriteAttributeString("profile", Constants.Namespaces.OpenSearch);
            writer.WriteElementString("title", response.SearchTitle);

            this.RenderSearchDescriptionAutodiscovery(writer);
            this.RenderSearchFeedsAutodiscovery(writer, response);
            this.RenderSearchResponseMetaTags(writer, response);

            writer.WriteEndElement();   // head

            writer.WriteStartElement("body");
            writer.WriteStartElement("ul");

            foreach (SearchResult result in response.Results)
            {
                writer.WriteStartElement("li");
                writer.WriteStartElement("a");
                writer.WriteAttributeString("href", result.Link);
                writer.WriteString(result.Title);
                writer.WriteEndElement();   // a
                writer.WriteElementString("div", result.Description);
                writer.WriteEndElement();   // li
            }

            writer.WriteEndElement();   // ul
            writer.WriteEndElement();   // body
            writer.WriteEndElement();   // html
            context.Response.End();




            //            <?xml version="1.0" encoding="UTF-8"?>
            //<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
            //   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
            //<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
            //  <head profile="http://a9.com/-/spec/opensearch/1.1/" >
            //    <title>Example.com Search: New York history</title>
            //    <link rel="search"
            //          type="application/opensearchdescription+xml" 
            //          href="http://example.com/opensearchdescription.xml"
            //          title="Example.com Web Search" />
            //    <meta name="totalResults" content="4230000"/>
            //    <meta name="startIndex" content"1"/>
            //    <meta name="itemsPerPage" content="10"/>
            //  </head>
            //  <body>
            //     <ul>
            //       <li>
            //         <a href="http://www.columbia.edu/cu/lweb/eguids/amerihist/nyc.html">
            //           New York History
            //         </a>
            //         <div>
            //           ... Harlem.NYC - A virtual tour and information on 
            //           businesses ...  with historic photos of Columbia's own New York 
            //           neighborhood ... Internet Resources for the City's History. ...
            //         </div>
            //       </li>
            //       <!-- ... -->
            //     </ul>
            //   </body>
            //</html>
        }



        /// <summary>
        /// Gets the actual content-type to use for the given content-type.  Allows subclasses to override defaults for debugging purposes, e.g. rendering application/rss+xml as text/xml.
        /// </summary>
        virtual protected string GetContentType(string preferredContentType)
        {
            return preferredContentType;
        }

        /// <summary>
        /// Gets an absolute url to this search engine's results for the given search information and format.
        /// </summary>
        protected string GetSearchUrl(string mimeType, string searchTerms, int pw)
        {
            string format = this.GetFormat(mimeType);

            if (format != null)
            {
                format = "&format=" + format;
            }

            return string.Format("{0}{1}?q={2}&pw={3}{4}", _handlerSettings.HttpBase, _handlerSettings.SearchPath, Util.UrlEncode(searchTerms), pw, format);
        }









































        private void RenderDescription(TextWriter textWriter)
        {

            OpenSearchDescription desc = _handlerSettings.OpenSearchServerDescription;


            XmlTextWriter writer = new XmlTextWriter(textWriter);



            writer.Formatting = Formatting.Indented;
            writer.WriteStartDocument();


            writer.WriteStartElement("OpenSearchDescription");
            writer.WriteAttributeString("xmlns", Constants.Namespaces.OpenSearch);


            if (desc.SearchFormUrl != null)
            {
                writer.WriteAttributeString("xmlns:moz", Constants.Namespaces.MozillaBrowserSearch);
            }


            writer.WriteElementString("ShortName", desc.ShortName);
            writer.WriteElementString("Description", desc.Description);
            writer.WriteElementString("Tags", string.Join(" ", desc.Tags.ToArray()));
            writer.WriteElementString("Contact", desc.Contact);



            foreach (Url url in desc.Urls)
            {
                writer.WriteStartElement("Url");
                writer.WriteAttributeString("type", url.Type);
                writer.WriteAttributeString("template", url.Template);
                writer.WriteAttributeString("indexOffset", url.IndexOffset.ToString());
                writer.WriteAttributeString("pageOffset", url.PageOffset.ToString());
                writer.WriteEndElement();
            }



            writer.WriteElementString("LongName", desc.LongName);

            foreach (Image image in desc.Images)
            {
                writer.WriteStartElement("Image");
                writer.WriteAttributeString("height", image.Height.ToString());
                writer.WriteAttributeString("width", image.Width.ToString());
                writer.WriteAttributeString("type", image.Type);
                writer.WriteString(image.Url);
                writer.WriteEndElement();
            }



            foreach (Query query in desc.Queries)
            {
                writer.WriteStartElement("Query");
                writer.WriteAttributeString("role", query.Role);
                writer.WriteAttributeString("searchTerms", query.SearchTerms);
                writer.WriteEndElement();
            }



            writer.WriteElementString("Developer", desc.Developer);
            writer.WriteElementString("Attribution", desc.Attribution);
            writer.WriteElementString("SyndicationRight", desc.SyndicationRight.ToString().ToLower());
            writer.WriteElementString("AdultContent", desc.AdultContent.ToString().ToLower());

            // defaults only for now
            writer.WriteElementString("Language", "*");
            writer.WriteElementString("OutputEncoding", "UTF-8");
            writer.WriteElementString("InputEncoding", "UTF-8");


            if (desc.SearchFormUrl != null)
            {
                writer.WriteElementString("moz:SearchForm", desc.SearchFormUrl);
            }

            writer.WriteEndElement();






        }



        private void ProcessImage(HttpContext context, string contentType, Func<Stream> source)
        {
            context.Response.ContentType = contentType;

            using (Stream input = source())
            {
                Util.BufferedTransfer(input, context.Response.OutputStream);
            }

            context.Response.End();

        }





        private void ProcessSearchAtom(HttpContext context, SearchResponse response)
        {


            context.Response.ContentType = this.GetContentType(Constants.MimeTypes.ApplicationAtomXml);

            XmlTextWriter writer = new XmlTextWriter(context.Response.Output);


            writer.Formatting = Formatting.Indented;

            writer.WriteStartDocument();


            writer.WriteStartElement("feed");
            writer.WriteAttributeString("xmlns", Constants.Namespaces.Atom);
            writer.WriteAttributeString("xmlns:opensearch", Constants.Namespaces.OpenSearch);

            writer.WriteElementString("title", response.SearchTitle);
            writer.WriteStartElement("link");
            writer.WriteAttributeString("href", response.SearchLink);
            writer.WriteEndElement();
            writer.WriteElementString("updated", Util.AtomDate(response.Updated));
            writer.WriteStartElement("author");
            writer.WriteElementString("name", response.SearchAuthorName);
            writer.WriteEndElement(); //author
            writer.WriteElementString("id", response.SearchID);

            writer.WriteElementString("opensearch:totalResults", response.TotalResults.ToString());
            writer.WriteElementString("opensearch:startIndex", response.StartIndex.ToString());
            writer.WriteElementString("opensearch:itemsPerPage", response.ItemsPerPage.ToString());

            writer.WriteStartElement("opensearch:Query");
            writer.WriteAttributeString("role", "request");
            writer.WriteAttributeString("searchTerms", response.SearchTerms);
            writer.WriteAttributeString("startPage", response.StartPage.ToString());
            writer.WriteEndElement();   // opensearch:Query


            

           


           

            writer.WriteStartElement("link");
            writer.WriteAttributeString("rel", "alternate");
            writer.WriteAttributeString("type", Constants.MimeTypes.TextHtml);
            writer.WriteAttributeString("href", this.GetSearchUrl(Constants.MimeTypes.TextHtml, response.SearchTerms, response.StartPage));
            writer.WriteEndElement();   // link

            writer.WriteStartElement("link");
            writer.WriteAttributeString("rel", "self");
            writer.WriteAttributeString("type", Constants.MimeTypes.ApplicationAtomXml);
            writer.WriteAttributeString("href", this.GetSearchUrl(Constants.MimeTypes.ApplicationAtomXml, response.SearchTerms, response.StartPage));
            writer.WriteEndElement();   // link

            writer.WriteStartElement("link");
            writer.WriteAttributeString("rel", "first");
            writer.WriteAttributeString("type", Constants.MimeTypes.ApplicationAtomXml);
            writer.WriteAttributeString("href", this.GetSearchUrl(Constants.MimeTypes.ApplicationAtomXml, response.SearchTerms, response.FirstPage));
            writer.WriteEndElement();   // link

            writer.WriteStartElement("link");
            writer.WriteAttributeString("rel", "previous");
            writer.WriteAttributeString("type", Constants.MimeTypes.ApplicationAtomXml);
            writer.WriteAttributeString("href", this.GetSearchUrl(Constants.MimeTypes.ApplicationAtomXml, response.SearchTerms, response.PreviousPage));
            writer.WriteEndElement();   // link

            writer.WriteStartElement("link");
            writer.WriteAttributeString("rel", "next");
            writer.WriteAttributeString("type", Constants.MimeTypes.ApplicationAtomXml);
            writer.WriteAttributeString("href", this.GetSearchUrl(Constants.MimeTypes.ApplicationAtomXml, response.SearchTerms, response.NextPage));
            writer.WriteEndElement();   // link

            writer.WriteStartElement("link");
            writer.WriteAttributeString("rel", "last");
            writer.WriteAttributeString("type", Constants.MimeTypes.ApplicationAtomXml);
            writer.WriteAttributeString("href", this.GetSearchUrl(Constants.MimeTypes.ApplicationAtomXml, response.SearchTerms, response.LastPage));
            writer.WriteEndElement();   // link



            writer.WriteStartElement("link");
            writer.WriteAttributeString("rel", "search");
            writer.WriteAttributeString("type", Constants.MimeTypes.ApplicationOpenSearchDescriptionXml);
            writer.WriteAttributeString("href", _handlerSettings.DescriptionUrl);
            writer.WriteEndElement();   // link


            foreach (SearchResult result in response.Results)
            {
                writer.WriteStartElement("entry");

                writer.WriteElementString("title", result.Title);
                writer.WriteStartElement("link");
                writer.WriteAttributeString("href", result.Link);
                writer.WriteEndElement();   // link
                writer.WriteElementString("id", result.ID);
                writer.WriteElementString("updated", Util.AtomDate(result.Updated));
                writer.WriteStartElement("content");
                writer.WriteAttributeString("type", "text");
                writer.WriteString(result.Description);
                writer.WriteEndElement();   // content

                writer.WriteEndElement();   // entry
            }



            writer.WriteEndElement();   // feed

            context.Response.End();


 //           <?xml version="1.0" encoding="UTF-8"?>
 //<feed xmlns="http://www.w3.org/2005/Atom" 
 //      xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/">
 //  <title>Example.com Search: New York history</title> 
 //  <link href="http://example.com/New+York+history"/>
 //  <updated>2003-12-13T18:30:02Z</updated>
 //  <author> 
 //    <name>Example.com, Inc.</name>
 //  </author> 
 //  <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>
 //  <opensearch:totalResults>4230000</opensearch:totalResults>
 //  <opensearch:startIndex>21</opensearch:startIndex>
 //  <opensearch:itemsPerPage>10</opensearch:itemsPerPage>
 //  <opensearch:Query role="request" searchTerms="New York History" startPage="1" />
 //  <link rel="alternate" href="http://example.com/New+York+History?pw=3" type="text/html"/>
 //  <link rel="self" href="http://example.com/New+York+History?pw=3&amp;format=atom" type="application/atom+xml"/>
 //  <link rel="first" href="http://example.com/New+York+History?pw=1&amp;format=atom" type="application/atom+xml"/>
 //  <link rel="previous" href="http://example.com/New+York+History?pw=2&amp;format=atom" type="application/atom+xml"/>
 //  <link rel="next" href="http://example.com/New+York+History?pw=4&amp;format=atom" type="application/atom+xml"/>
 //  <link rel="last" href="http://example.com/New+York+History?pw=42299&amp;format=atom" type="application/atom+xml"/>
 //  <link rel="search" type="application/opensearchdescription+xml" href="http://example.com/opensearchdescription.xml"/>
 //  <entry>
 //    <title>New York History</title>
 //    <link href="http://www.columbia.edu/cu/lweb/eguids/amerihist/nyc.html"/>
 //    <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
 //    <updated>2003-12-13T18:30:02Z</updated>
 //    <content type="text">
 //      ... Harlem.NYC - A virtual tour and information on 
 //      businesses ...  with historic photos of Columbia's own New York 
 //      neighborhood ... Internet Resources for the City's History. ...
 //    </content>
 //  </entry>
 //</feed>
        }


        private void ProcessSearchRss(HttpContext context,SearchResponse response)
        {


            context.Response.ContentType = this.GetContentType(Constants.MimeTypes.ApplicationRssXml);

            XmlTextWriter writer = new XmlTextWriter(context.Response.Output);


            writer.Formatting = Formatting.Indented;

            writer.WriteStartDocument();


            writer.WriteStartElement("rss");
            writer.WriteAttributeString("version", "2.0");
            writer.WriteAttributeString("xmlns:opensearch", Constants.Namespaces.OpenSearch);
            writer.WriteAttributeString("xmlns:atom", Constants.Namespaces.Atom);


            writer.WriteStartElement("channel");

            writer.WriteElementString("title", response.SearchTitle);
            writer.WriteElementString("link", response.SearchLink);
            writer.WriteElementString("description", response.SearchDescription);
            writer.WriteElementString("opensearch:totalResults", response.TotalResults.ToString());
            writer.WriteElementString("opensearch:startIndex", response.StartIndex.ToString());
            writer.WriteElementString("opensearch:itemsPerPage", response.ItemsPerPage.ToString());

            writer.WriteStartElement("atom:link");
            writer.WriteAttributeString("rel", "search");
            writer.WriteAttributeString("type", Constants.MimeTypes.ApplicationOpenSearchDescriptionXml);
            writer.WriteAttributeString("href",_handlerSettings.DescriptionUrl);
            writer.WriteEndElement();   // atom:link

            writer.WriteStartElement("opensearch:Query");
            writer.WriteAttributeString("role", "request");
            writer.WriteAttributeString("searchTerms", response.SearchTerms);
            writer.WriteAttributeString("startPage", response.StartPage.ToString());
            writer.WriteEndElement();   // opensearch:Query


            foreach (SearchResult result in response.Results)
            {
                writer.WriteStartElement("item");
                writer.WriteElementString("title", result.Title);
                writer.WriteElementString("link", result.Link);
                writer.WriteElementString("description", result.Description);
                writer.WriteEndElement();   // item
            }

           

            writer.WriteEndElement();   // channel

            writer.WriteEndElement();   // rss


 //           <rss version="2.0" 
 //     xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"
 //     xmlns:atom="http://www.w3.org/2005/Atom">
 //  <channel>
 //    <title>Example.com Search: New York history</title>
 //    <link>http://example.com/New+York+history</link>
 //    <description>Search results for "New York history" at Example.com</description>
 //    <opensearch:totalResults>4230000</opensearch:totalResults>
 //    <opensearch:startIndex>21</opensearch:startIndex>
 //    <opensearch:itemsPerPage>10</opensearch:itemsPerPage>
 //    <atom:link rel="search" type="application/opensearchdescription+xml" href="http://example.com/opensearchdescription.xml"/>
 //    <opensearch:Query role="request" searchTerms="New York History" startPage="1" />
 //    <item>
 //      <title>New York History</title>
 //      <link>http://www.columbia.edu/cu/lweb/eguids/amerihist/nyc.html</link>
 //      <description>
 //        ... Harlem.NYC - A virtual tour and information on 
 //        businesses ...  with historic photos of Columbia's own New York 
 //        neighborhood ... Internet Resources for the City's History. ...
 //      </description>
 //    </item>
 //  </channel>
 //</rss>




            context.Response.End();

        }


        private void ProcessSearch(HttpContext context)
        {
            string searchTerms = context.Request.QueryString["q"];
            string format = context.Request.QueryString["format"];
            string pw = context.Request.QueryString["pw"];


            int startPage;
            if (!int.TryParse(pw, out startPage))
            {
                startPage = 1;
            }



            SearchResponse response = this.Search(context, startPage, searchTerms);


           
            if (format == "rss")
            {
                this.ProcessSearchRss(context, response);
            }
            if (format == "atom")
            {
                this.ProcessSearchAtom(context, response);
            }

            else
            {
                this.ProcessSearchXhtml(context, response);
            }
        }



      

        private void ProcessDescription( HttpContext context)
        {
            context.Response.ContentType = this.GetContentType(Constants.MimeTypes.ApplicationOpenSearchDescriptionXml);
            this.RenderDescription(context.Response.Output);
            context.Response.End();
        }


        

        private bool ProcessSuggest(HttpContext context)
        {


            context.Response.ContentType = this.GetContentType(Constants.MimeTypes.ApplicationXSuggestionsJson);

            string q = context.Request.QueryString["q"];

            List<string> suggestions = new List<string>(this.GetSuggestions(q));

            suggestions = suggestions.ConvertAll<string>(Util.JsonQuote);

            string rt = Util.JsonArray(
                Util.JsonQuote(q), 
                Util.JsonArray(suggestions));

            context.Response.Write(rt);

            context.Response.End();

            return true;
        }




       


        private string GetFormat(string mimeType)
        {
            if (mimeType == Constants.MimeTypes.ApplicationAtomXml)
            {
                return "atom";
            }
            if (mimeType == Constants.MimeTypes.ApplicationRssXml)
            {
                return "rss";
            }
            return null;
        }


      
    }
}
